package com.liliudong.rommath.biz.sys.service.impl;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.StrUtil;
import com.liliudong.rommath.biz.sys.context.UserContext;
import com.liliudong.rommath.biz.sys.entity.SysMenu;
import com.liliudong.rommath.biz.sys.mapper.SysMenuMapper;
import com.liliudong.rommath.biz.sys.param.SysMenuAddParam;
import com.liliudong.rommath.biz.sys.param.SysMenuModifyParam;
import com.liliudong.rommath.biz.sys.result.SysMenuChildrenResult;
import com.liliudong.rommath.biz.sys.result.SysMenuDetailResult;
import com.liliudong.rommath.biz.sys.result.SysMenuRoutesResult;
import com.liliudong.rommath.biz.sys.service.SysMenuService;
import com.liliudong.rommath.biz.sys.service.SysRoleMenuService;
import com.liliudong.rommath.common.consts.RommathConst;
import com.liliudong.rommath.common.exception.BizException;
import com.liliudong.rommath.common.security.util.SecurityUtil;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.liliudong.rommath.biz.sys.entity.table.SysMenuTableDef.SYS_MENU;
import static com.liliudong.rommath.biz.sys.entity.table.SysRoleMenuTableDef.SYS_ROLE_MENU;
import static com.liliudong.rommath.biz.sys.entity.table.SysRoleTableDef.SYS_ROLE;
import static com.liliudong.rommath.biz.sys.entity.table.SysUserRoleTableDef.SYS_USER_ROLE;

/**
 * 系统菜单 服务层实现。
 *
 * @author liudong.li
 * @since 1.0.0
 */
@Service
@RequiredArgsConstructor
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {

    private final SysRoleMenuService sysRoleMenuService;

    @Override
    public List<String> selectPermissionsByUseIdIgnoreDataPermission(Long userId) {
        boolean superAdmin = UserContext.isSuperAdmin(userId);
        if (superAdmin) {
            return Collections.singletonList(RommathConst.SUPER_ADMIN_PERMISSION);
        }
        return queryChainIgnoreDataPermission()
                .select(SYS_MENU.PERMISSION)
                .innerJoin(SYS_ROLE_MENU).on(SYS_ROLE_MENU.MENU_ID.eq(SYS_MENU.ID))
                .innerJoin(SYS_USER_ROLE).on(SYS_USER_ROLE.ROLE_ID.eq(SYS_ROLE_MENU.ROLE_ID))
                .where(
                        SYS_USER_ROLE.USER_ID.eq(userId)
                )
                .listAs(String.class)
                .stream()
                .filter(Objects::nonNull)
                .toList();
    }

    @Override
    public List<Tree<Long>> routes() {
        long loginUserId = SecurityUtil.getLoginUserId();
        boolean superAdmin = UserContext.isSuperAdmin(loginUserId);
        if (superAdmin) {
            List<SysMenu> sysMenus = queryChainIgnoreDataPermission().list();

            return buildRouteTreeList(sysMenus);
        }
        List<SysMenu> sysMenus = queryChainIgnoreDataPermission()
                .innerJoin(SYS_ROLE_MENU).on(SYS_ROLE_MENU.MENU_ID.eq(SYS_MENU.ID))
                .innerJoin(SYS_USER_ROLE).on(SYS_USER_ROLE.ROLE_ID.eq(SYS_ROLE_MENU.ROLE_ID))
                .where(
                        SYS_USER_ROLE.USER_ID.eq(loginUserId)
                                .and(SYS_MENU.SHOW_LINK.eq(1))
                )
                .list();
        return buildRouteTreeList(sysMenus);

    }

    @Override
    @Transactional
    public void add(SysMenuAddParam param) {
        SysMenu sysMenu = SysMenu.of(param);
        saveOrUpdateCheck(sysMenu);
        save(sysMenu);
        if (Objects.equals(sysMenu.getMenuType(), SysMenu.MenuType.MENU)) {
            List<SysMenuAddParam.Button> buttonList = param.getButtonList();
            if (CollUtil.isNotEmpty(buttonList)) {
                List<SysMenu> sysMenus = buttonList.stream()
                        .map(button -> {
                            SysMenu sysMenuButton = new SysMenu();
                            sysMenuButton.setParentId(sysMenu.getId());
                            sysMenuButton.setMenuType(SysMenu.MenuType.BUTTON);
                            sysMenuButton.setName(button.getName());
                            sysMenuButton.setPermission(button.getPermission());
                            return sysMenuButton;
                        })
                        .toList();
                saveBatch(sysMenus);
            }
        }
    }

    @Override
    @Transactional
    public void deleteById(Long id) {
        // 存在子节点不能删除
        boolean existChildren = queryChainIgnoreDataPermission()
                .where(SYS_MENU.PARENT_ID.eq(id))
                .where(SYS_MENU.MENU_TYPE.ne(SysMenu.MenuType.BUTTON))
                .exists();
        if (existChildren) {
            throw new BizException("存在子节点不能删除");
        }
        // 菜单被角色关联的列表
        List<String> roles = sysRoleMenuService.queryChainIgnoreDataPermission()
                .select(SYS_ROLE.NAME)
                .innerJoin(SYS_ROLE).on(SYS_ROLE.ID.eq(SYS_ROLE_MENU.ROLE_ID))
                .where(SYS_ROLE_MENU.MENU_ID.eq(id))
                .listAs(String.class);
        if (CollUtil.isNotEmpty(roles)) {
            throw new BizException("菜单被角色关联：" + roles);
        }
        removeById(id);
        // 删除下按钮
        remove(SYS_MENU.PARENT_ID.eq(id).and(SYS_MENU.MENU_TYPE.eq(SysMenu.MenuType.BUTTON)));
    }

    @Override
    @Transactional
    public void modify(SysMenuModifyParam param) {
        SysMenu sysMenu = SysMenu.of(param);
        saveOrUpdateCheck(sysMenu);
        // 更新菜单
        updateById(sysMenu);
        // 更新按钮
        if (Objects.equals(sysMenu.getMenuType(), SysMenu.MenuType.MENU)) {
            List<SysMenuModifyParam.Button> buttonList = param.getButtonList();
            // 删除菜单下的按钮
            remove(SYS_MENU.PARENT_ID.eq(sysMenu.getId())
                    .and(SYS_MENU.MENU_TYPE.eq(SysMenu.MenuType.BUTTON)));
            if (CollUtil.isNotEmpty(buttonList)) {
                List<SysMenu> sysMenus = buttonList.stream()
                        .map(button -> {
                            SysMenu sysMenuButton = new SysMenu();
                            sysMenuButton.setParentId(sysMenu.getId());
                            sysMenuButton.setMenuType(SysMenu.MenuType.BUTTON);
                            sysMenuButton.setName(button.getName());
                            sysMenuButton.setPermission(button.getPermission());
                            return sysMenuButton;
                        })
                        .toList();
                saveBatch(sysMenus);
            }
        }
    }

    @Override
    public List<Tree<Long>> tree(List<SysMenu.MenuType> excludeTypes) {
        List<SysMenu> sysMenus = queryChainIgnoreDataPermission()
                .where(SYS_MENU.MENU_TYPE.notIn(excludeTypes, CollUtil.isNotEmpty(excludeTypes)))
                .list();
        return buildTreeList(sysMenus);
    }

    /**
     * 构建树列表
     *
     * @param sysMenus sys菜单
     * @return {@link List }<{@link Tree }<{@link Long }>>
     */
    private List<Tree<Long>> buildTreeList(List<SysMenu> sysMenus) {
        if (sysMenus.isEmpty()) {
            return List.of();
        }

        List<TreeNode<Long>> treeNodes = sysMenus.stream()
                .map(sysMenu -> {
                    TreeNode<Long> treeNode = new TreeNode<>(sysMenu.getId(), sysMenu.getParentId(), sysMenu.getName(), sysMenu.getRank());
                    SysMenuRoutesResult sysMenuRoutesResult = SysMenuRoutesResult.of(sysMenu);
                    Map<String, Object> map = sysMenuRoutesResult.toMap();
                    treeNode.setExtra(map);
                    return treeNode;
                })
                .toList();
        return TreeUtil.build(treeNodes, RommathConst.ROOT_MENU_ID);
    }

    @Override
    public List<SysMenuChildrenResult> children(Long id) {
        if (Objects.isNull(id)) {
            return queryChainIgnoreDataPermission()
                    .where(SYS_MENU.MENU_TYPE.ne(SysMenu.MenuType.BUTTON))
                    .listAs(SysMenuChildrenResult.class);
        }
        return queryChainIgnoreDataPermission()
                .where(SYS_MENU.PARENT_ID.eq(id))
                .where(SYS_MENU.MENU_TYPE.ne(SysMenu.MenuType.BUTTON))
                .listAs(SysMenuChildrenResult.class);
    }

    @Override
    public SysMenuDetailResult detailById(Long id) {
        SysMenuDetailResult sysMenuDetailResult = queryChain().where(SYS_MENU.ID.eq(id)).oneAs(SysMenuDetailResult.class);
        if (Objects.isNull(sysMenuDetailResult)) {
            throw new BizException("菜单不存在");
        }
        // 获取子级按钮
        List<SysMenuDetailResult.Button> buttonList = queryChain()
                .where(SYS_MENU.PARENT_ID.eq(id).and(SYS_MENU.MENU_TYPE.eq(SysMenu.MenuType.BUTTON)))
                .listAs(SysMenuDetailResult.Button.class);
        sysMenuDetailResult.setButtonList(buttonList);
        return sysMenuDetailResult;
    }

    /**
     * 构建路由树列表
     *
     * @param sysMenus sys菜单
     * @return {@link List }<{@link Tree }<{@link Long }>>
     */
    private static List<Tree<Long>> buildRouteTreeList(List<SysMenu> sysMenus) {
        if (sysMenus.isEmpty()) {
            return List.of();
        }
        Map<Long, List<SysMenu>> buttonsMap = sysMenus.stream()
                .filter(sysMenu -> Objects.equals(sysMenu.getMenuType(), SysMenu.MenuType.BUTTON))
                .collect(Collectors.groupingBy(SysMenu::getParentId));

        List<TreeNode<Long>> treeNodes = sysMenus.stream()
                .map(sysMenu -> {
                    if (Objects.equals(sysMenu.getMenuType(), SysMenu.MenuType.BUTTON)) {
                        return null;
                    }
                    TreeNode<Long> treeNode = new TreeNode<>(sysMenu.getId(), sysMenu.getParentId(), sysMenu.getName(), sysMenu.getRank());
                    SysMenuRoutesResult sysMenuRoutesResult = SysMenuRoutesResult.of(sysMenu);
                    List<SysMenu> buttons = buttonsMap.get(sysMenu.getId());
                    if (CollUtil.isNotEmpty(buttons)) {
                        SysMenuRoutesResult.Meta meta = sysMenuRoutesResult.getMeta();
                        if (Objects.nonNull(meta)) {
                            List<String> auths = buttons.stream().map(SysMenu::getPermission).toList();
                            meta.setAuths(auths);
                        }
                    }
                    Map<String, Object> map = sysMenuRoutesResult.toMap();
                    treeNode.setExtra(map);
                    return treeNode;
                })
                .filter(Objects::nonNull)
                .toList();
        return TreeUtil.build(treeNodes, RommathConst.ROOT_MENU_ID);
    }

    /**
     * 保存或更新检查
     *
     * @param sysMenu sys菜单
     */
    private void saveOrUpdateCheck(SysMenu sysMenu) {
        SysMenu.MenuType menuType = sysMenu.getMenuType();
        String name = sysMenu.getName();
        Long id = sysMenu.getId();
        String title = sysMenu.getTitle();
        String path = sysMenu.getPath();
        String permission = sysMenu.getPermission();
        if (SysMenu.MenuType.MENU.equals(menuType) ||
                SysMenu.MenuType.IFRAME.equals(menuType) ||
                SysMenu.MenuType.LINK.equals(menuType)) {
            if (StrUtil.isEmpty(title)) {
                throw new BizException("菜单名称不能为空");
            }
            if (StrUtil.isEmpty(path)) {
                throw new BizException("路由路径不能为空");
            }
        }
        if (SysMenu.MenuType.BUTTON.equals(menuType)) {
            if (StrUtil.isEmpty(title)) {
                throw new BizException("按钮名称不能为空");
            }
            if (StrUtil.isEmpty(permission)) {
                throw new BizException("权限标识不能为空");
            }
        }
        long counted = queryChainIgnoreDataPermission()
                .where(SYS_MENU.NAME.eq(name).and(SYS_MENU.MENU_TYPE.ne(SysMenu.MenuType.BUTTON)))
                .where(SYS_MENU.ID.ne(id, Objects.nonNull(id)))
                .count();
        if (counted > 0) {
            throw new BizException("路由名称必须保持唯一");
        }
    }
}